package com.jmebananas.game.console;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

import javacream.resource.ResourceException;
import javacream.resource.ResourceManager;

import com.jme.image.Texture;
import com.jme.input.KeyInput;
import com.jme.input.KeyInputListener;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.Text;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmebananas.game.tile.TileText;
import com.jmebananas.scene.SpringTranslationController;
import com.jmebananas.util.Constants;
import com.jmebananas.util.UniqueName;

/**
 * Console
 * 
 * @author Glenn Powell
 *
 */
public class Console extends Node implements KeyInputListener {

	private static final long serialVersionUID = 1L;
	
	
	private static final Logger logger = Logger.getLogger(Console.class.getName());
	
	
	public static final int DEFAULT_MAX_HISTORY = 256;
	
	public static final int DEFAULT_MAX_COMMAND_HISTORY = 64;
	
	private static final float LINE_HEIGHT = 14.0f;
	

	private int key;

	private LinkedList<String> history;

	private LinkedList<String> commandHistory;

	private int maxHistory;

	private int maxCommandHistory;

	private int historyPosition;

	private int commandHistoryPosition;

	private boolean echo;

	private boolean active;

	private Text[] rows;

	private TileText entry;

	private TileText cursor;

	private StringBuffer cursorBuffer;

	private StringBuffer current;

	private int cursorPosition;

	private Text prompt;

	private SpringTranslationController controller;

	private Vector3f activePosition;

	private Vector3f inactivePosition;

	private ArrayList<ConsoleListener> listeners;


	public Console() {
		this(null, 1, KeyInput.KEY_GRAVE, 10, false);
	}

	public Console(int key, int rows, boolean echo) {
		this(null, 1, key, rows, echo);
	}

	public Console(Texture fontTexture, float fontScale, int key, int rows, boolean echo) {
		super(UniqueName.createName("console"));

		this.key = key;
		this.rows = new Text[rows];
		this.echo = echo;
		
		active = false;
		history = new LinkedList<String>();
		commandHistory = new LinkedList<String>();
		maxHistory = DEFAULT_MAX_HISTORY;
		maxCommandHistory = DEFAULT_MAX_COMMAND_HISTORY;
		commandHistoryPosition = -1;
		float height = ((rows + 1) * LINE_HEIGHT) * fontScale + 8.0f;
		activePosition = new Vector3f(0, 0, 0);
		inactivePosition = new Vector3f(0, -height, 0);
		listeners = new ArrayList<ConsoleListener>();
		cursorBuffer = new StringBuffer(40);
		cursorBuffer.append("_");
		current = new StringBuffer(40);

		ZBufferState zBufferState = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
		zBufferState.setEnabled(false);
		setRenderState(zBufferState);

		getLocalTranslation().set(inactivePosition);
		controller = new SpringTranslationController(400, getLocalTranslation());
		addController(controller);

		if (fontTexture == null) {
			try {
				fontTexture = TextureManager.loadTexture(ResourceManager.getURL(Constants.DEFAULT_FONT_PATH),
						Texture.MM_LINEAR, Texture.FM_LINEAR);
			} catch (ResourceException e) {
				logger.log(Level.SEVERE, "Error loading font for Default Font", e);
			}
		}

		TextureState fontState = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
		if (fontTexture != null)
			fontState.setTexture(fontTexture);
		fontState.setEnabled(true);
		
		int displayWidth = DisplaySystem.getDisplaySystem().getWidth();

		Quad quad = new Quad(getName() + "_bg", displayWidth, height);
		quad.setDefaultColor(new ColorRGBA(0.0f, 0, 0, 0.5f));
		quad.setLocalTranslation(displayWidth * 0.5f, height * 0.5f, 0);
		attachChild(quad);

		float y = LINE_HEIGHT * fontScale + 4.0f;
		for (int i = 0; i < rows; i++) {
			this.rows[i] = attachText(getName() + "_line" + i, fontState, fontScale, "", 10.0f, y);
			y += LINE_HEIGHT * fontScale;
		}
		entry = attachText(getName() + "_entry", fontState, fontScale, "", 10.0f, y);
		cursor = attachText(getName() + "_cursor", fontState, fontScale, cursorBuffer.toString(), 0.0f, y);
		prompt = attachText(getName() + "_prompt", fontState, fontScale, "", -2.0f, y);
		setPrompt(">");

		updateRenderState();
		
		KeyInput.get().addListener(this);
	}
	
	public int getKey() {
		return key;
	}
	
	public void setKey(int key) {
		this.key = key;
	}

	public boolean isActive() {
		return active;
	}
	
	public void setActive(boolean active) {
		this.active = active;
		controller.translate(active ? activePosition : inactivePosition);
	}
	
	public int getMaxHistory() {
		return maxHistory;
	}
	
	public void setMaxHistory(int maxHistory) {
		this.maxHistory = maxHistory;
	}
	
	public int getMaxCommandHistory() {
		return maxCommandHistory;
	}
	
	public void setMaxCommandHistory(int maxCommandHistory) {
		this.maxCommandHistory = maxCommandHistory;
	}

	public void setPrompt(String promptStr) {
		prompt.print(promptStr);

		cursor.getLocalTranslation().x = prompt.getWidth();
		entry.getLocalTranslation().x = prompt.getWidth();
	}

	private TileText attachText(String name, TextureState textureState, float scale, String value, float xPosition, float yPosition) {
		TileText text = new TileText(name, value);
		text.setTextColor(ColorRGBA.yellow);
		text.setLocalScale(new Vector3f(scale, -scale, 1));
		text.setTextureCombineMode(TextureState.REPLACE);
		text.setRenderState(textureState);
		text.setLocalTranslation(xPosition, yPosition, 0);
		attachChild(text);
		return text;
	}

	public void type(char character) {
		if (Character.isISOControl(character))
			return;

		current.insert(cursorPosition++, character);
		cursorBuffer.insert(0, ' ');
		entry.print(current);
		cursor.print(cursorBuffer);
	}

	public void setText(String text) {
		current.delete(0, current.length());
		current.append(text);
		cursorBuffer.delete(0, cursorBuffer.length());
		cursorBuffer.append("_");
		for (int i = 0; i < text.length() - 1; i++) {
			cursorBuffer.insert(0, ' ');
		}
		cursorPosition = text.length();
		entry.print(current);
		cursor.print(cursorBuffer);
	}

	public void backspace() {
		if ((current.length() > 0) && (cursorPosition > 0)) {
			current.deleteCharAt(--cursorPosition);
			cursorBuffer.deleteCharAt(0);
			entry.print(current);
			cursor.print(cursorBuffer);
		}
	}

	public void enter() {
		if (current.length() > 0) {
			String s = current.toString();
			execute(s);
			if (commandHistory.size() >= maxCommandHistory) {
				commandHistory.poll();
			}
			commandHistory.add(s);
			current.delete(0, current.length());
			cursorBuffer.delete(0, cursorBuffer.length());
			cursorBuffer.append("_");
			cursorPosition = 0;
			commandHistoryPosition = -1;
			entry.print(current);
			cursor.print(cursorBuffer);
		}
	}

	public void execute(String command) {
		if (echo)
			log(command);
		else
			logger.info(command);

		for (ConsoleListener listener : listeners) {
			listener.consoleExecuted(this, command);
		}
	}

	public void log(String message) {
		if (history.size() >= maxHistory) {
			history.poll();
		}
		history.add(message);
		if (historyPosition != 0)
			historyPosition++;
		
		logger.info(message);

		updateHistory();
	}

	public void updateHistory() {
		int position = history.size() - rows.length - historyPosition;
		for (int i = 0; i < rows.length; i++) {
			if (position < 0) {
				rows[i].print("");
			} else {
				rows[i].print(history.get(position));
			}
			position++;
		}
	}

	public void moveUpInHistory() {
		if (historyPosition < history.size() - rows.length) {
			historyPosition++;
			updateHistory();
		}
	}

	public void moveDownInHistory() {
		if (historyPosition > 0) {
			historyPosition--;
			updateHistory();
		}
	}

	public boolean moveCursorLeft() {
		if (cursorPosition > 0) {
			cursorPosition--;
			cursorBuffer.deleteCharAt(0);
			cursor.print(cursorBuffer);
			return true;
		}
		return false;
	}

	public void moveCursorHome() {
		while (moveCursorLeft())
			;
	}

	public boolean moveCursorRight() {
		if (cursorPosition < current.length()) {
			cursorPosition++;
			cursorBuffer.insert(0, ' ');
			cursor.print(cursorBuffer);
			return true;
		}
		return false;
	}

	public void moveCursorEnd() {
		while (moveCursorRight())
			;
	}

	public void moveUpInCommandHistory() {
		if (commandHistory.size() > 0) {
			if (commandHistoryPosition == 0)
				return;
			if (commandHistoryPosition == -1) {
				commandHistoryPosition = commandHistory.size();
			}
			commandHistoryPosition--;
			setText(commandHistory.get(commandHistoryPosition));
		}
	}

	public void moveDownInCommandHistory() {
		if (commandHistory.size() > 0) {
			if (commandHistoryPosition == commandHistory.size() - 1) {
				commandHistoryPosition = -1;
				setText("");
				return;
			}
			if (commandHistoryPosition == -1)
				return;
			commandHistoryPosition++;
			setText(commandHistory.get(commandHistoryPosition));
		}
	}

	public void onKey(char character, int keyCode, boolean pressed) {
		if (pressed) {
			if (keyCode == key) {
				setActive(!active);
			} else if (!active) {
				return;
			} else if (keyCode == KeyInput.KEY_BACK) {
				backspace();
			} else if (keyCode == KeyInput.KEY_RETURN) {
				enter();
			} else if (keyCode == KeyInput.KEY_PGUP) {
				moveUpInHistory();
			} else if (keyCode == KeyInput.KEY_PGDN) {
				moveDownInHistory();
			} else if (keyCode == KeyInput.KEY_LEFT) {
				moveCursorLeft();
			} else if (keyCode == KeyInput.KEY_RIGHT) {
				moveCursorRight();
			} else if (keyCode == KeyInput.KEY_UP) {
				moveUpInCommandHistory();
			} else if (keyCode == KeyInput.KEY_DOWN) {
				moveDownInCommandHistory();
			} else if (keyCode == KeyInput.KEY_HOME) {
				moveCursorHome();
			} else if (keyCode == KeyInput.KEY_END) {
				moveCursorEnd();
			} else {
				type(character);
			}
		}
	}

	public boolean addConsoleListener(ConsoleListener listener) {
		return listeners.add(listener);
	}

	public boolean removeListener(ConsoleListener listener) {
		return listeners.remove(listener);
	}
}